package cn.vin.ws;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class TimeProfile {
    private static final Logger LOG = LoggerFactory.getLogger(TimeProfile.class);
    private static final ThreadLocal<LinkedList<TimeProfileElement>> stack = new ThreadLocal<>();
    private static final ThreadLocal<LinkedList<TimeProfileElement>> logs = new ThreadLocal<>();
    private static final ThreadLocal<Integer> currentThreshold = new ThreadLocal<>();
    private static volatile int threshold = 100;// 超时时间，单位毫秒
    private static final String timeout = "TimeProfile timeout ";

    /**
     * 线程开始
     */
    public static void start() {
        stack.set(new LinkedList<>());
        logs.set(new LinkedList<>());
        enter("");
    }

    /**
     * 方法体前
     * @param tag
     */
    public static void enter(String tag) {
        if (stack.get() == null) {
            return;
        }
        TimeProfileElement t = new TimeProfileElement();
        t.setEnterTime(System.currentTimeMillis());
        t.setTag(tag);
        t.setDeep(stack.get().size());

        stack.get().push(t);
    }

    /**
     * 在需要监控耗时的方法体后调用
     */
    public static void release() {
        LinkedList<TimeProfileElement> s1 = stack.get();
        if (s1 == null) {
            return;
        }
        if(s1.size() <= 1){
            clear();
            return;
        }
        TimeProfileElement t = stack.get().pop();
        t.setReleaseTime(System.currentTimeMillis());
        logs.get().add(t);
    }

    /**
     * 线程结束时
     */
    public static void end() {
        LinkedList<TimeProfileElement> s1 = stack.get();
        if(s1.size() != 1){
            //enter release必须成对存在
            clear();
            return;
        }
        TimeProfileElement t = s1.pop();
        t.setReleaseTime(System.currentTimeMillis());
        logs.get().add(t);

        long timeConsume = t.getCostTimeMillis();
        Integer threshold = currentThreshold.get();
        if(threshold == null){
            threshold = TimeProfile.threshold;
        }
        if (t.getCostTimeMillis() > threshold) {
            // 输出日志
            StringBuilder sb = new StringBuilder();
            sb.append(timeout).append(timeConsume).append("ms > thread name:").append(Thread.currentThread().getName());
            List<TimeProfileElement> list = new ArrayList<>(logs.get());


            Collections.reverse(list);
            for (TimeProfileElement s : list) {
                sb.append("\r\n\t");
                for (int i = 0; i < s.getDeep(); i++) {
                    sb.append("-");
                }
                Long consume = s.getCostTimeMillis();
                sb.append(consume * 100 / timeConsume).append("%");
                sb.append(" ").append(consume).append("ms");
                if (s.getTag() != null) {
                    sb.append(" ").append(s.getTag());
                }
            }
            LOG.warn("{}",sb);
        }
        clear();
    }

    public static void clear() {
        stack.set(null);
        logs.set(null);
        currentThreshold.set(null);
    }

    /**
     * 设置报警阈值，这是全局配置，只要在应用启动时设置一次即可,单位：ms
     * <br/>
     * 当接入spring-boot-ext框架时，请使用配置:duiba.profile.threshold 来设置
     * @param threshold
     */
    public static void setThreshold(int threshold) {
        TimeProfile.threshold = threshold;
    }

    /**
     * 设置当前线程调用栈的报警阈值，这个配置只会在当前线程、本次监控生效。
     * @param threshold
     */
    public static void setCurrentThreshold(int threshold) {
        currentThreshold.set(threshold);
    }

    public static void main(String[] args) throws InterruptedException {
        TimeProfile.start();
        TimeProfile.enter("fun1");
        Thread.sleep(30);
        TimeProfile.release();
        TimeProfile.enter("fun2");
        Thread.sleep(80);
        TimeProfile.release();
        TimeProfile.end();
    }


    static class TimeProfileElement {
        private long enterTime;
        private String tag;
        private int deep;
        private long releaseTime;

        public long getCostTimeMillis() {
            return releaseTime - enterTime;
        }
        public long getReleaseTime() {
            return releaseTime;
        }

        public void setReleaseTime(long releaseTime) {
            this.releaseTime = releaseTime;
        }

        public long getEnterTime() {
            return enterTime;
        }

        public void setEnterTime(long enterTime) {
            this.enterTime = enterTime;
        }

        public String getTag() {
            return tag;
        }

        public void setTag(String tag) {
            this.tag = tag;
        }

        public int getDeep() {
            return deep;
        }

        public void setDeep(int deep) {
            this.deep = deep;
        }
    }
}
